#ifndef _SELECT_SERVER_H_
#define _SELECT_SERVER_H_

#include <iostream>
#include <sys/select.h>
#include <sys/time.h>
#include "sock.hpp"

#define BITS 8
#define NUM (sizeof(fd_set) * BITS)
#define FD_NONE -1

using namespace std;
// 实现select读取
class SelectServer
{
public:
    SelectServer(const uint16_t &port = 8080)
        : _port(port)
    {
        _listenSock = Sock::Socket();
        Sock::Bind(_listenSock, _port);
        Sock::Listen(_listenSock);
        logMessage(DEBUG, "%s", "create base socket success");
        for (int i = 0; i < NUM; i++)
            _fd_read_arr[i] = FD_NONE;

        _fd_read_arr[0] = _listenSock; // 规定_fd_arr[0]=_listenSock
    }

    void Start()
    {
        while (true)
        {
            // listen套接字是为了获取新连接，因此是读事件
            // int sock = Sock::Accpet(_listenSock, ...);
            // 不能直接accept，要select等后，accept直接获取资源

            // struct timeval timeout = {2, 0};
            // FD_SET(_listenSock, &rfds);
            // int n = select(_listenSock + 1, &rfds, nullptr, nullptr, &timeout); // timeout是nullptr表示阻塞式等待

            // 1. nfds: 随着我们获取的sock越来越多，随着我们添加到select的sock越来越多，注定了nfds每一次都可能要变化,我们需要对它动态计算
            // 2. rfds/writefds/exceptfds：都是输入输出型参数，输入输出不一定以一样的，所以注定了我们每一次都要对rfds进行重新添加
            // 3. timeout: 都是输入输出型参数，每一次都要进行重置，前提是你要的话
            // 1,2 => 注定了我们必须自己将合法的文件描述符需要单独全部保存起来
            // 用来支持：1. 更新最大fd 2.更新位图结构

            DebugPrint();

            fd_set rfds;
            FD_ZERO(&rfds);
            int maxfd = _listenSock;
            for (int i = 0; i < NUM; i++)
            {
                if (_fd_read_arr[i] == FD_NONE)
                    continue;
                FD_SET(_fd_read_arr[i], &rfds);
                if (maxfd < _fd_read_arr[i])
                    maxfd = _fd_read_arr[i];
            }

            int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);
            switch (n)
            {
            case 0:
                logMessage(DEBUG, "%s", "time out……");
                break;
            case -1:
                logMessage(WARNING, "select error:%d  %s", errno, strerror(errno));
                break;
            default:
                logMessage(DEBUG, "get a new link event..."); // 为何一直打印link event? -连接已就绪，但没使用，select会一直提醒
                HandlerEvent(rfds);
                break;
            }
        }
    }

    ~SelectServer()
    {
        if (_listenSock >= 0)
            close(_listenSock);
    }

private:
    void HandlerEvent(const fd_set &rfds) // rdfs有多个sock
    {
        for (int i = 0; i < NUM; i++)
        {
            if (_fd_read_arr[i] == FD_NONE)
                continue; // 跳过非法fd
            // 合法fd
            if (FD_ISSET(_fd_read_arr[i], &rfds))
            {
                // 指定的fd read就绪
                if (i == 0 && _fd_read_arr[i] == _listenSock)
                    // 读事件就绪，连接时间accept
                    Accepter();
                else
                    Recver(i);
            }
        }
    }

    void Accepter()
    {
        string clientip;
        uint16_t clientport = 0;
        // if (FD_ISSET(_listenSock, &rfds)) // 在rfds
        {
            // 获取新连接
            int sock = Sock::Accpet(_listenSock, &clientip, &clientport);
            if (sock < 0)
            {
                logMessage(WARNING, "accept error...");
                return;
            }
            logMessage(DEBUG, "get a new link success:[%s : %d] %d",
                       clientip.c_str(), clientport, sock);

            // 此时不能read和recv，因为不知道sock上面的数据什么时候来，会阻塞
            // 但select最清楚有没有数据
            // 所以得到新连接后，将新的sock交给select
            // 将sock添加到select中，只需要将fd放到数组中即可

            int pos = 1;
            for (pos = 1; pos < NUM; pos++)
            {
                if (_fd_read_arr[pos] == FD_NONE)
                    break;
            }
            if (pos == NUM)
            {
                logMessage(WARNING, "%s ,%d ", "select server already full ,close: %d", sock);
                close(sock);
            }
            else
            {
                _fd_read_arr[pos] = sock;
            }
        }
    }

    void Recver(int pos)
    {
        // 读事件就绪，input事件到来，read和recv使用
        logMessage(DEBUG, "message in, get IO event: %d", _fd_read_arr[pos]);

        // 此时select已经帮我们进行了事件检测，fd上的数据一定是就绪的，即 本次 不会被阻塞
        char buff[1024];
        int n = recv(_fd_read_arr[pos], buff, sizeof(buff), 0);
        if (n > 0)
        {
            buff[n] = 0;
            logMessage(DEBUG, "client [%d] : %s-", _fd_read_arr[pos], buff);
        }
        else if (n == 0)
        {
            logMessage(DEBUG, "client [%d] quit,me too...", _fd_read_arr[pos]);
            // 关闭不需要的fd
            close(_fd_read_arr[pos]);
            // 关闭当前的fd关心
            _fd_read_arr[pos] = FD_NONE;
        }
        else
        {
            logMessage(WARNING, "%d sock recv error,%d:%s", _fd_read_arr[pos], errno, strerror(errno));
            // 关闭不需要的fd
            close(_fd_read_arr[pos]);
            // 关闭当前的fd关心
            _fd_read_arr[pos] = FD_NONE;
        }
    }

    void DebugPrint()
    {
        cout << "_fd_read_arr[]: ";
        for (int i = 0; i < NUM; i++)
        {
            if (_fd_read_arr[i] == FD_NONE)
                continue;
            cout << _fd_read_arr[i] << " ";
        }
        cout << endl;
    }

private:
    uint16_t _port;
    int _listenSock;
    int _fd_read_arr[NUM];
};

#endif